package main

import (
	"crypto/md5"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

const (
	DAHUA_PROTO_DHIP          = 0x5049484400000020
	DAHUA_REALM_DHIP          = 268632079
	DEFAULT_KEEPALIVEINTERVAL = 60
)

type DahuaVTOClient struct {
	requestID           int64
	sessionID           int64
	username            string
	password            string
	configManagerAttach bool
	conn                net.Conn
	callCallback        func(client *DahuaVTOClient, userId string)
	userIds             []string
	// 连接相关信
	host           string
	port           int
	connectTimeout time.Duration
	// 状态管理
	connected bool
	flag      bool

	disconnected      int32 // 主动断开连接标志
	reconnected       bool
	reconnectAttempts int
	reconnecting      sync.Mutex

	chunk             []byte
	keepAliveInterval *int
	heartbeatTicker   *time.Ticker
	onResponseChans   sync.Map
	attrs             map[string]interface{}

	// 重连配置
	reconnectInterval time.Duration
	maxReconnect      int
}

func (c *DahuaVTOClient) MarshalJSON() ([]byte, error) {
	type Alias DahuaVTOClient
	return json.Marshal(&struct {
		*Alias
		Connected bool     `json:"connected"`
		Host      string   `json:"host"`
		Port      int      `json:"port"`
		UserIDs   []string `json:"userids"`
	}{
		Alias:     (*Alias)(c),
		Connected: c.IsConnected(),
		Host:      c.host,
		Port:      c.port,
		UserIDs:   c.userIds,
	})
}

func NewDahuaVTOClient(username, password string, userIds []string, cb func(client *DahuaVTOClient, userId string), configManagerAttach bool) *DahuaVTOClient {
	return &DahuaVTOClient{
		username:            username,
		password:            password,
		userIds:             userIds,
		callCallback:        cb,
		configManagerAttach: configManagerAttach,
		chunk:               make([]byte, 0),
		onResponseChans:     sync.Map{},
		reconnectInterval:   5 * time.Second, // 默认重连间隔
		maxReconnect:        0,               // 默认最大重连次数
	}
}

// SetReconnectParams 设置重连参数
func (c *DahuaVTOClient) SetReconnectParams(interval time.Duration, maxReconnect int) {
	c.reconnectMutexLock()
	defer c.reconnectMutexUnlock()
	c.reconnectInterval = interval
	c.maxReconnect = maxReconnect
}

// Connect 连接
func (c *DahuaVTOClient) Connect(host string, port int, timeout time.Duration) error {
	c.host = host
	c.port = port
	c.connectTimeout = timeout
	atomic.StoreInt32(&c.disconnected, 0)
	return c.connectInternal()
}

// 内部连接逻辑
func (c *DahuaVTOClient) connectInternal() error {
	c.reconnectMutexLock()
	defer c.reconnectMutexUnlock()

	if c.conn != nil {
		c.conn.Close()
		c.conn = nil
	}
	c.connected = false
	c.requestID = 0
	c.sessionID = 0
	c.onResponseChans.Range(func(key, value interface{}) bool {
		value.(*CallRes).Close()
		c.onResponseChans.Delete(key)
		return true
	})

	conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", c.host, c.port), c.connectTimeout)
	if err != nil {
		return fmt.Errorf("failed to connect: %v", err)
	}

	c.conn = conn
	c.connected = true
	c.flag = false
	c.reconnectAttempts = 0
	c.chunk = make([]byte, 0)

	logger.Println("Successfully connected to device")

	go c.handleConnection()

	c.send(map[string]interface{}{
		"method": "global.login",
		"params": map[string]interface{}{
			"clientType": "",
			"ipAddr":     "(null)",
			"loginType":  "Direct",
		},
	}, false)
	return nil
}

func (c *DahuaVTOClient) hashedPassword(random, realm string) string {
	firstHash := md5.Sum([]byte(fmt.Sprintf("%s:%s:%s", c.username, realm, c.password)))
	firstHex := strings.ToUpper(hex.EncodeToString(firstHash[:]))

	secondHash := md5.Sum([]byte(fmt.Sprintf("%s:%s:%s", c.username, random, firstHex)))
	return strings.ToUpper(hex.EncodeToString(secondHash[:]))
}

func (c *DahuaVTOClient) handleConnection() {
	buf := make([]byte, 4096)
	for {
		n, err := c.conn.Read(buf)
		if err != nil {
			logger.Printf("Connection error: %v", err)
			c.handleDisconnection()
			return
		}
		if cap(c.chunk)-len(c.chunk) < n {
			newChunk := make([]byte, len(c.chunk), len(c.chunk)+n)
			copy(newChunk, c.chunk)
			c.chunk = newChunk
		}
		c.chunk = append(c.chunk, buf[:n]...)
		c.processData()
	}
}

// 处理断开连接
func (c *DahuaVTOClient) handleDisconnection() {
	c.closeConnection()
	// 如果是主动断开，不重连
	if atomic.LoadInt32(&c.disconnected) == 1 {
		logger.Println("Connection closed by user, not reconnecting")
		return
	}
	// 处理重连
	c.reconnect()
}

// 实际关闭连接
func (c *DahuaVTOClient) closeConnection() {
	if c.conn != nil {
		c.conn.Close()
		c.conn = nil
	}
	c.connected = false
	if c.heartbeatTicker != nil {
		c.heartbeatTicker.Stop()
		c.heartbeatTicker = nil
	}
	// 预分配固定大小的chunk
	if cap(c.chunk) > 4096 { // 如果缓冲区过大则释放
		c.chunk = nil
	} else {
		c.chunk = c.chunk[:0] // 重用已有缓冲区
	}
	// 清理所有等待响应的通道
	c.onResponseChans.Range(func(key, value interface{}) bool {
		value.(*CallRes).Close()
		c.onResponseChans.Delete(key)
		return true
	})
}

// 自动重连
func (c *DahuaVTOClient) reconnect() {
	c.reconnectMutexLock()
	if c.reconnected {
		c.reconnectMutexUnlock()
		return
	}
	c.reconnected = true
	c.reconnectMutexUnlock()
	defer func() {
		c.reconnectMutexLock()
		c.reconnected = false
		c.reconnectMutexUnlock()
	}()
	for {
		if atomic.LoadInt32(&c.disconnected) == 1 {
			return
		}
		if c.maxReconnect > 0 && c.reconnectAttempts >= c.maxReconnect {
			logger.Printf("Maximum reconnect attempts (%d) reached", c.maxReconnect)
			return
		}
		c.reconnectAttempts++
		time.Sleep(c.reconnectInterval)
		logger.Printf("Trying to reconnect (attempt %d/%d)...", c.reconnectAttempts, c.maxReconnect)
		if err := c.connectInternal(); err == nil {
			logger.Printf("connect ok")
			return
		}
	}
}

type ReceiveParams struct {
	SID               int              `json:"SID"`
	EventList         []map[string]any `json:"eventList"`
	Random            string           `json:"random"`
	Realm             string           `json:"realm"`
	KeepAliveInterval int              `json:"keepAliveInterval"`
}

type ReceiveRes struct {
	Method  string                  `json:"method"`
	Params  *ReceiveParams          `json:"params"`
	Session int64                   `json:"session"`
	Id      int64                   `json:"id"`
	Error   *map[string]interface{} `json:"error"`
	Result  any                     `json:"result"`
}

func (c *DahuaVTOClient) processData() {
	headerSize := 32 // Size of DAHUA_HEADER_FORMAT in bytes
	for len(c.chunk) >= headerSize {
		proto := binary.LittleEndian.Uint64(c.chunk[0:8])
		if proto != DAHUA_PROTO_DHIP {
			logger.Printf("Wrong proto: %x", proto)
			return
		}

		packetLen := binary.LittleEndian.Uint64(c.chunk[24:32])
		totalLen := headerSize + int(packetLen)

		if len(c.chunk) < totalLen {
			return
		}

		packet := c.chunk[headerSize:totalLen]

		tail := totalLen
		if len(c.chunk)-tail == headerSize {
			tail += headerSize
		}

		c.chunk = c.chunk[tail:]

		var message ReceiveRes
		if err := json.Unmarshal(packet, &message); err != nil {
			logger.Printf("Error decoding message: %v", err)
			continue
		}
		logger.Printf("<<< %v", string(packet))
		c.receive(message)
	}
}

func (c *DahuaVTOClient) receive(message ReceiveRes) {
	params := message.Params
	if message.Error != nil {
		errorData := *message.Error
		if code, ok := errorData["code"].(float64); ok && int(code) == DAHUA_REALM_DHIP {
			c.sessionID = message.Session
			random := params.Random
			realm := params.Realm
			c.send(map[string]interface{}{
				"method": "global.login",
				"params": map[string]interface{}{
					"userName":   c.username,
					"password":   c.hashedPassword(random, realm),
					"clientType": "",
					"ipAddr":     "(null)",
					"loginType":  "Direct",
				},
				"session": c.sessionID,
			}, false)
		} else {
			logger.Printf("Error: %v", message.Error)
		}
	} else if message.Id > 0 && message.Id != 3 {
		id := message.Id
		if val, exists := c.onResponseChans.Load(id); exists {
			cr := val.(*CallRes)
			select {
			case cr.Ch() <- message:
			default:
			}
			cr.Close()
			c.onResponseChans.Delete(id)
		}
		if id == 2 {
			if params != nil {
				if params.KeepAliveInterval > 0 {
					c.keepAliveInterval = &params.KeepAliveInterval
				}
			}
			if c.keepAliveInterval == nil {
				defaultInterval := DEFAULT_KEEPALIVEINTERVAL
				c.keepAliveInterval = &defaultInterval
				logger.Printf("Using default keepAliveInterval (%d)", defaultInterval)
			}

			c.startHeartbeat()
			c.send(map[string]interface{}{
				"method": "eventManager.attach",
				"params": map[string]interface{}{"codes": []string{"All"}},
			}, false)

			if c.configManagerAttach {
				c.send(map[string]interface{}{
					"method": "configManager.attach",
					"params": map[string]interface{}{"name": "CommGlobal"},
				}, false)
			}
		}
	} else if message.Method != "" {
		switch message.Method {
		case "client.notifyEventStream":
			//忽略启动第一条消息
			if !c.flag {
				c.flag = true
				return
			}
			if message.Params == nil || message.Params.EventList == nil {
				return
			}
			for _, event := range message.Params.EventList {
				code, ok := event["Code"].(string)
				if !ok || code != "CallNoAnswered" {
					continue
				}
				data, ok := event["Data"].(map[string]interface{})
				if !ok {
					continue
				}
				userid, ok := data["UserID"].(string)
				if !ok {
					continue
				}
				for _, v := range c.userIds {
					if v == userid {
						c.callCallback(c, v)
					}
				}
			}
		case "client.notifyConfigChange":
		}
	}
}

type CallRes struct {
	ch chan ReceiveRes
	sync.Once
}

func (c *CallRes) Ch() chan ReceiveRes {
	return c.ch
}

func (c *CallRes) Close() {
	c.Do(func() {
		close(c.ch)
	})
}

func (c *DahuaVTOClient) send(message map[string]interface{}, response bool) (chan ReceiveRes, int64) {
	// 如果未连接，直接返回
	if !c.IsConnected() {
		if response {
			return nil, 0
		}
		return nil, 0
	}

	id := atomic.AddInt64(&c.requestID, 1)
	message["id"] = id
	message["session"] = c.sessionID
	ch := make(chan ReceiveRes, 1)
	if response {
		c.onResponseChans.Store(id, &CallRes{
			ch: ch,
		})
	}
	data, err := json.Marshal(message)
	if err != nil {
		logger.Printf("Error encoding message: %v", err)
		return nil, 0
	}

	logger.Printf(">>> %s", string(data))

	header := make([]byte, 32)
	binary.LittleEndian.PutUint64(header[0:8], DAHUA_PROTO_DHIP)
	binary.LittleEndian.PutUint32(header[8:12], uint32(c.sessionID))
	binary.LittleEndian.PutUint32(header[12:16], uint32(c.requestID))
	binary.LittleEndian.PutUint64(header[16:24], uint64(len(data)))
	binary.LittleEndian.PutUint64(header[24:32], uint64(len(data)))

	_, err = c.conn.Write(append(header, data...))
	if err != nil {
		logger.Printf("Error sending message: %v", err)
	}
	if ch != nil {
		return ch, id
	}
	return nil, id
}

func (c *DahuaVTOClient) Command(message map[string]interface{}, resp any, timeout time.Duration) error {
	ch, id := c.send(message, true)
	if ch == nil {
		return fmt.Errorf("not connected")
	}

	select {
	case response := <-ch:
		if response.Id == id {
			if resp != nil {
				msgData, _ := json.Marshal(response)
				return json.Unmarshal(msgData, &resp)
			}
			return nil
		}
		return fmt.Errorf("response ID mismatch")
	case <-time.After(timeout):
		return fmt.Errorf("timeout")
	}
}

type InstanceResp struct {
	Id      int `json:"id"`
	Result  int `json:"result"`
	Session int `json:"session"`
}

func (c *DahuaVTOClient) CancelCall(timeout time.Duration) error {
	// 发送console.runCmd命令执行"hc"来取消通话
	err := c.Command(map[string]interface{}{
		"method": "console.runCmd",
		"params": map[string]interface{}{
			"command": "hc",
		},
	}, nil, timeout)
	if err != nil {
		return fmt.Errorf("failed to cancel call: %v", err)
	}
	return nil
}

// OpenDoorAndCancelCall 开门并取消通话
func (c *DahuaVTOClient) OpenDoorAndCancelCall(shortNumber string, timeout time.Duration) error {
	// 开门
	err := c.OpenDoor(shortNumber, timeout)
	if err != nil {
		return fmt.Errorf("failed to open door: %v", err)
	}
	time.Sleep(time.Second)
	// 取消通话
	err = c.CancelCall(timeout)
	if err != nil {
		logger.Printf("failed to cancel call Error: %v", err)
	}
	return nil
}

func (c *DahuaVTOClient) OpenDoor(shortNumber string, timeout time.Duration) error {
	// channel - 1 是因为 Python 代码中 channel 参数减 1 传递
	instanceParams := map[string]interface{}{
		"channel": 0,
	}
	params := map[string]interface{}{
		"DoorIndex":   0,
		"ShortNumber": shortNumber,
	}
	var instanceResp InstanceResp
	// 先调用 factory.instance 获取 object id
	err := c.Command(map[string]interface{}{
		"method": "accessControl.factory.instance",
		"params": instanceParams,
	}, &instanceResp, timeout)
	if err != nil {
		return err
	}
	objectID := instanceResp.Result
	if objectID == 0 {
		return fmt.Errorf("failed to get object id")
	}
	var openDoorResp struct {
		Session int  `json:"session"`
		Id      int  `json:"id"`
		Result  bool `json:"result"`
	}
	// 调用 openDoor 方法
	err = c.Command(map[string]interface{}{
		"method": "accessControl.openDoor",
		"object": objectID,
		"params": params,
	}, &openDoorResp, timeout)
	if err != nil {
		return err
	}
	if !openDoorResp.Result {
		return fmt.Errorf("open door error")
	}
	// 最后调用 destroy 销毁 object
	_ = c.Command(map[string]interface{}{
		"method": "accessControl.destroy",
		"object": objectID,
	}, nil, timeout)
	return nil
}

func (c *DahuaVTOClient) startHeartbeat() {
	if c.heartbeatTicker != nil {
		c.heartbeatTicker.Stop()
	}
	c.heartbeatTicker = time.NewTicker(30 * time.Second)

	go func() {
		for range c.heartbeatTicker.C {
			if !c.IsConnected() {
				return
			}
			err := c.Command(map[string]interface{}{
				"method": "global.keepAlive",
				"params": map[string]interface{}{
					"timeout": *c.keepAliveInterval,
					"action":  true,
				},
			}, nil, 3*time.Second)
			if err != nil {
				logger.Printf("Heartbeat error: %v", err)
				c.handleDisconnection()
				return
			}
		}
	}()
}

func (c *DahuaVTOClient) Close() {
	// 标记为主动断开
	atomic.StoreInt32(&c.disconnected, 1)
	c.closeConnection()
}

// 检查连接状态
func (c *DahuaVTOClient) IsConnected() bool {
	return c.connected && c.conn != nil
}

// 重连互斥锁操作
func (c *DahuaVTOClient) reconnectMutexLock() {
	c.reconnecting.Lock()
}

func (c *DahuaVTOClient) reconnectMutexUnlock() {
	c.reconnecting.Unlock()
}
